#! /usr/bin/python
#
# $Id$
#
# RTEMS Project (http://www.rtems.org/)
# Copyright 2008
# All rights reserved.
#
# This file is part of The RTEMS Tools.
#
# The RTEMS Tools is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# The RTEMS Tools is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with The RTEMS Tools; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# The Generator tool generate C code to wrap up self contained
# functions
#

import fileinput
import fnmatch
import optparse
import os
import re
import sys

import config
import environ
import error
import execute
import command
import log
#
# Executed from the command line.
#
command_line = __name__ == "__main__"

# The directory in which the generated wrap files are stored
generated_dir = 'generated'

# The boolean whether the function return void
returnvoid = False

# The keyword in C language, any type not in this list need to
# fetch type information to generate code like "typedef ..."
keywords = [
        'unsigned',
        'long',
        'int'
]

# The input file for function list to wrap up
funcfile = "symbols_func.txt"

# The input file for type information of arguments for wrap function
typefile = 'symbols_typedef.txt'
structfile = 'symbols_struct.txt'


def output(text = None, l = None):
    """Output text if run from the command line. If not run from the
    command line or quite do not print the text."""
    if command_line:
        log.output(text, l)
        
class generator:
    def __init__(self):
        self.table = {}
        
    def loadline(self,line):
        """Load one line in function list to wrap up"""
        sl = line.strip().split(':',1)
        if len(sl) != 2:
            raise error.general('invalid line')
        fd = {}
        fd['name'] = sl[0]
        sl_proto = sl[1].strip().split(sl[0]+'(',1)
        fd['return'] = sl_proto[0]
        args = sl_proto[1].split(');')[0]
        arglist = []
        while True:
            al = args.strip().split(',',1)
            arg = al[0].strip().split(' ')
            arglist.append(arg)
            if len(al) <= 1:
                break;
            args = al[1].strip()
        fd['args'] = arglist
        return fd
            
            
    def load(self, funcfile):
        """Load the function to wrap up"""
        if funcfile is None:
            raise error.general('no function list provided')
        if not os.path.exists(funcfile):
            raise error.general('cannot find function list file: ' + funcfile)
        for line in fileinput.input(funcfile):
            if line.strip() != '':
                fd = self.loadline(line)
                self.table[fd['name']] = fd
        fileinput.close()
            
    def print_typedef_decl(self, argtype, logfile):
        """For each function to wrap up, to be self contained
        so that no Rtems header file need to be included, put
        required typedef onto the generated file"""
        global typefile
        
        found = False
        for line in fileinput.input(typefile):
            if line.startswith(argtype.strip()+':'):
                td = 'typedef '
                td_tmp = line.strip().split(':', 1)[1].rsplit(';', 1)[0].strip()
                # todo - to handle with union 
                if td_tmp.startswith('struct'):
                    td_tmp2 = td_tmp.split(' ', 2)
                    if len(td_tmp2) != 3:
                        output('error: print_typedef_decl - len is not 3')
                    else:
                        td_tmp = 'struct ' + td_tmp2[2]
                td += td_tmp
                # if argtype startswith %anon, remove % for C grammar
                # Some %anon could not be rename to actual one
                # for example, 'rtems_time_of_day * time_buffer'
                # in function rtems_clock_set
                _argtype = argtype.strip().split('%',1)
                td += ' ' + _argtype[len(_argtype)-1] + ';'
                output(td, logfile)
                found = True
                break
        fileinput.close()
        
        if not found:
            for line in fileinput.input(structfile):
                if line.startswith(argtype.strip()+':'):
                    td = 'typedef '
                    td_tmp = line.strip().split(':', 1)[1].rsplit(';', 1)[0].strip()
                    # todo - to handle with union 
                    if td_tmp.startswith('struct'):
                        td_tmp2 = td_tmp.split(' ', 2)
                        if len(td_tmp2) != 3:
                            output('error: print_typedef_decl - len is not 3')
                        else:
                            td_tmp = 'struct ' + td_tmp2[2]
                    td += td_tmp
                    _argtype = argtype.strip().split('%',1)
                    td += ' ' + _argtype[len(_argtype)-1] + ';'
                    output(td, logfile)
            fileinput.close()
        
    def print_header(self, func, logfile):
        """Print the header in the generated file"""
        output('/*', logfile)
        output(' *  Wrapper for ' + self.table[func]['name'], logfile)
        output(' *', logfile)
        output(' *  $ID$', logfile)
        output(' */', logfile)
        output('\n', logfile)
        output('#include "trace_log.h"', logfile)
        output('#include "trace_trigger.h"', logfile)
        output('\n', logfile)
        if self.table[func]['return'] not in keywords:
            self.print_typedef_decl(self.table[func]['return'], logfile)
        for arg in self.table[func]['args']:
            for i in range(0,len(arg)-1):
                if arg[i] not in keywords:
                    self.print_typedef_decl(arg[i], logfile)
        output('\n', logfile)

        
    def print_real(self, func, logfile):
        """Print prototype of __real_ function"""
        self.dump_fd(func, 'real', logfile)
        output(';', logfile)
        
    def print_wrap_decl(self, func, logfile):
        """Print the Declaration of Wrapper function __wrap_"""
        self.dump_fd(func, 'wrap', logfile)
        
    def print_wrap_retvar(self, func, logfile):
        global returnvoid
        """Print the Variable to carry return value"""
        if self.table[fd]['return'] == 'void':
            returnvoid = True
        if not returnvoid:
            output('  ' + self.table[fd]['return'] + 'ret;', logfile)
        
    def members(self, sname, filename):
        for line in fileinput.input(filename):
            sl = line.split(':',1)
            if sname == sl[0].strip():
                sl2 = sl[1].split('{', 1)
                sl3 = sl2[1].rsplit('};', 1)
                fileinput.close()
                return sl3[0].rsplit(';',1)[0].split(';')
        fileinput.close()
        output('warning: members() - ' + sname + ' ' + filename)
        return None
                
    def members_struct(self, sname):
        """Return the list of members of struct in which each member is a string
        of type and name like 'long unsigned int mem_name'
        input: sname - the struct name
        The struct info is from symbols_struct.txt"""
        global structfile
        return self.members(sname, structfile)
        
    def members_typedef(self, sname):
        """The difference with members_struct is:
        the file to locate member info is symbols_typedef.txt"""
        global typefile
        ret = self.members(sname, typefile)
        if ret is not None:
            return ret
        else:
            # todo: to handle with union
            return self.members(sname, structfile) 
    
    def get_typedef(self, sname):
        """return 'struct' for structs
           return 'union' for unions
           otherwise return the type info like 'long unsigned int'
        """
        global typefile
        for line in fileinput.input(typefile):
            sl = line.split(':',1)
            if sname == sl[0].strip():
                if sl[1].strip().startswith('struct'):
                    fileinput.close()
                    return 'struct'
                elif sl[1].strip().startswith('union'):
                    fileinput.close()
                    return 'union'
                elif sl[1].strip().startswith('enum'):
                    fileinput.close()
                    return 'enum'
                else:
                    fileinput.close()
                    return sl[1].strip().rsplit(';',1)[0]
        fileinput.close()
        for line in fileinput.input(structfile):
            sl = line.split(':',1)
            if sname == sl[0].strip():
                fileinput.close()
                return 'struct'
        # todo: handle with union
        output('error: get_typedef() - ' + sname)
        return None        
        
    def print_arg(self, arg, logfile, sname=None):
        """Print specified arg according to arg type
        sname is the struct name if applicable
        """
        pre_arg = ''  # pre_arg could be 'struct_name.' or 'struct_name->'
        if sname is not None:
            pre_arg = sname
        if '(*' in arg:
            """Not to trace the value of func pointer"""
            output('func pointer: ' + arg[1])
        if 'int' in arg:
            output('    ' + 'trace_log("  ' + pre_arg + arg[len(arg)-1] + ' = %d\\n", ' + pre_arg + arg[len(arg)-1] + ');', logfile)
        elif 'char' in arg:
            if arg[len(arg)-1].startswith('*'):
                argname = arg[len(arg)-1].split('*', 1)[1].strip()
                output('    ' + 'trace_log("  ' + pre_arg + argname + ' = %s\\n", ' + pre_arg + argname + ');', logfile)
            else:
                output('    ' + 'trace_log("  ' + pre_arg + arg[len(arg)-1] + ' = %c\\n", ' + pre_arg + arg[len(arg)-1] + ');', logfile)
        elif 'void' in arg:
            if arg[len(arg)-1].startswith('*'):
                """for 'void *', simply output the address of pointer"""
                argname = arg[len(arg)-1].split('*', 1)[1].strip()
                output('    ' + 'trace_log("  ' + pre_arg + argname + ' = %d\\n", ' + pre_arg + argname + ');', logfile)
            else:
                output('warning: void type could not be outputed')
        elif 'struct' in arg:
            if arg[0] != 'struct':
                output('warning: ' + arg[0] + ' while struct expected')
            if len(arg) != 3:
                output('warning: ' + arg[1] + ' do not have expected format')
            if arg[2].startswith('*'):
                argname = arg[2].split('*', 1)[1].strip()
                argtype = arg[1]
                for arg_mem in self.members_struct(argtype):
                    self.print_arg(arg_mem.strip().split(' '), logfile, pre_arg+argname+'->')
            else:
                argname = arg[2].strip()
                argtype = arg[1]
                for arg_mem in self.members_struct(argtype):
                    self.print_arg(arg_mem.strip().split(' '), logfile, pre_arg+argname+'.')
        elif 'union' in arg:
            """todo"""
            output('todo')
        else:
            """The type should be one from typedef"""
            if len(arg) != 2:
                output('warning: ' + arg[0] + ' ' + arg[1] + ' do not have expected format')
            argtype = arg[0].strip()
            argname = ''
            conn_str = '.'
            if arg[1].startswith('*'):
                argname = arg[1].split('*', 1)[1].strip()
                conn_str = '->'
            else:
                argname = arg[1].strip()
            typeinfo = self.get_typedef(argtype)
            if  typeinfo == 'struct' or typeinfo == 'union' :
                for arg_mem in self.members_typedef(argtype):
                    self.print_arg(arg_mem.strip().split(' '), logfile, pre_arg+argname+conn_str)
            elif typeinfo == 'enum':
                output('    ' + 'trace_log("  ' + pre_arg + arg[1] + ' = %d\\n", ' + pre_arg + arg[1] + ');', logfile)
            else:
                """typeinfo is something like 'long unsigned int'"""
                arg2 = typeinfo.split(' ')
                arg2.append(arg[1])
                self.print_arg(arg2, logfile, pre_arg)
        
    def print_wrap_entry(self, func, logfile):
        """Print at the entry"""
        output('  ' + 'bool bTrig = isTriggered("' + self.table[fd]['name'] + '");', logfile)
        output('  ' + 'if (bTrig)', logfile)
        output('  ' + '{', logfile)
        output('    ' + 'trace_log("' + self.table[fd]['name'] + ' entered\\n");', logfile)
        for arg in self.table[func]['args']:
            self.print_arg(arg, logfile)
        output('  ' + '}', logfile)
        
    def print_wrap_invocation(self, func, logfile):
        """Print the invocation of real function"""
        global returnvoid
        invoc = '  '
        if not returnvoid:
            invoc += 'ret = '
        invoc += '__real_' + self.table[fd]['name'] + '('
        output(invoc, logfile)
        delimiter = ''
        invoc = ''
        for arg in self.table[fd]['args']:
            argname = arg[len(arg)-1]
            if argname.startswith('*'):
                argname = argname.split('*', 1)[1].strip()
            invoc += delimiter + '    ' + argname
            delimiter = ',\n'
        invoc += '\n  );'
        output(invoc, logfile)
        
    def print_wrap_exit(self, func, logfile):
        """Print before the exit"""
        output('  ' + 'if (bTrig)', logfile)
        output('  ' + '{', logfile)
        output('    ' + 'trace_log("' + self.table[fd]['name'] + ' returned\\n");', logfile)
        for arg in self.table[func]['args']:
            self.print_arg(arg, logfile)
        output('  ' + '}', logfile)
        
    def print_wrap_return(self, func, logfile):
        """If applicable, print the return statement"""
        global returnvoid
        if not returnvoid:
            output('  return ret;', logfile)
            
    def generate(self, func):
        """Generate wrap file for specified function"""
        l = log.log(['generated' + os.sep + 'wrap_'+func+'.c'])
        self.print_header(func, l)
        self.print_real(func, l)
        output('\n', l)
        self.print_wrap_decl(func, l)
        output('{', l)
        self.print_wrap_retvar(func, l)
        self.print_wrap_entry(func, l)
        self.print_wrap_invocation(func, l)
        self.print_wrap_exit(func, l)
        self.print_wrap_return(func, l)
        output('}', l)
        
    def dump_fd(self, fd, functype, logfile):
        """Dump the functions in table for real or wrap"""
        decl = self.table[fd]['return'] + ' __' + functype + '_' + self.table[fd]['name']
        delimiter = '(\n  '
        for arg in self.table[fd]['args']:
            decl += delimiter
            delimiter2 = ''
            for i in range(0,len(arg)):
                # remove % from '%anon'
                argi = arg[i].split('%',1)
                decl += delimiter2 + argi[len(argi)-1]
                delimiter2 = ' '
            delimiter = ',\n  '
        decl += '\n)'
        output(decl, logfile)
        
        

if command_line:
    obj = generator()
    obj.load(funcfile)
    if os.path.isdir(generated_dir):
        pass
    elif os.path.isfile(generated_dir):
        raise error.general('a file with same name exists')
    else:
        os.mkdir(generated_dir)
    for fd in obj.table:
        obj.generate(fd)
    del obj